package com.atguigu.gmall.seckill.task;

import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.seckill.mapper.SecKillGoodsMapper;
import com.atguigu.gmall.seckill.util.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * projectName: gmall
 *
 * @author: 陈鹏
 * time: 2022/10/17 18:11 星期一
 * description: 将秒杀商品提前放入redis中的定时任务
 */
@Component
public class SeckillGoodsFromDbToRedisTask {

    @Autowired
    private RedisTemplate redisTemplate;

    @Resource
    private SecKillGoodsMapper secKillGoodsMapper;

    /**
     * 秒杀商品写入redis中
     *  cron= 秒 分 时 日 月 周 年(省略)
     *  *: 任意时间
     *  ?: 忽略这个时间
     *  直接写数字: 指定时间执行
     *  逗号: 指定多个时间执行
     *  -: 区间
     *  /: 间隔
     *  fixedDelay: 每5秒执行一次, 受方法的执行时间影响(方法执行完以后开始计时)
     *  fixedRate:  每5秒执行一次, 不受方法的执行时间影响(方法执行开始后就执行计时)
     *  initialDelay: 只影响第一次什么时候执行
     *
     */
    @Scheduled(cron = "1/30 * * * * *")
    public void seckillGoodsFromDbToRedis(){
        //计算时间段,计算当前系统所在的时间段以及后面4个时间段
        List<Date> dateMenus = DateUtil.getDateMenus();
        //查询5个时间的商品分别放入每个时间段的redis中去
        dateMenus.stream().forEach(date -> {
            //计算当前开始时间: 2022-10-17 10:00
            String startTime = DateUtil.data2str(date, DateUtil.PATTERN_YYYY_MM_DDHHMM);
            //获取结束时间日志
            Date endDate = DateUtil.addDateHour(date,2);
            //计算key的存活时间
            Long liveTime = endDate.getTime() - System.currentTimeMillis();
            //计算当前结束时间: 2022-10-17 12:00
            String endTime =
                    DateUtil.data2str(endDate, DateUtil.PATTERN_YYYY_MM_DDHHMM);
            //计算当前时间段用于redis存储数据的key= 2022101710
            String key = DateUtil.data2str(date, DateUtil.PATTERN_YYYYMMDDHH);
            //查询数据库中这个时间段的全部的商品数据
            LambdaQueryWrapper<SeckillGoods> wrapper = new LambdaQueryWrapper<SeckillGoods>();
            //商品必须审核通过
            wrapper.eq(SeckillGoods::getStatus,1);
            //2022-10-17 10:00<=start_time
            wrapper.ge(SeckillGoods::getStartTime,startTime);
            // end_time<=2022-10-17 12:00
            wrapper.le(SeckillGoods::getEndTime,endTime);
            //商品必须要有库存
            wrapper.gt(SeckillGoods::getStockCount,0);
            //redis中没有的
            Set keys = redisTemplate.opsForHash().keys(key);
            if(keys != null && keys.size() > 0){
                wrapper.notIn(SeckillGoods::getId,keys);
            }
            //查询秒杀商品的数据
            List<SeckillGoods> seckillGoodsList = secKillGoodsMapper.selectList(wrapper);
            //判断是否为空
            if(!StringUtils.isEmpty(seckillGoodsList)){
                seckillGoodsList.stream().forEach(seckillGoods -> {
                    //将数据写入redis中去
                    redisTemplate.opsForHash().put(key,seckillGoods.getId()+"",seckillGoods);
                    //为每个商品构建一个队列,同时队列的元素个数就是这个商品的剩余库存--用于用户下单的依据
                    redisTemplate.opsForList().leftPushAll("Seckill_Goods_Queue_" + seckillGoods.getId(),
                            getIds(seckillGoods.getId()+"",seckillGoods.getStockCount()));
                    //设置队列过期
                    redisTemplate.expire("Seckill_Goods_Queue_" + seckillGoods.getId(),liveTime,TimeUnit.MILLISECONDS);
                    //再去构建一个库存的自增值----1.更新redis中的商品数据中的商品库存 2.同步剩余库存到数据库
                    redisTemplate.opsForHash().increment("Seckill_Goods_Stock_" + key,
                            seckillGoods.getId() + "",
                            seckillGoods.getStockCount());
                });
            }
            //设置过期时间
            setLiveTime(liveTime,key);
        });
    }

    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 设置过期时间
     * @param liveTime
     * @param key
     */
    private void setLiveTime(Long liveTime, String key) {
        //每个时间段的过期时间只设置一次
        Long count =
                redisTemplate.opsForHash().increment("Set_Live_Time_Count", key, 1);
        if(count > 1){
            return;
        }
        //设置时间段商品数据过期
        redisTemplate.expire(key,liveTime, TimeUnit.MILLISECONDS);
        //发送消息,用于同步商品的库存到数据库
        rabbitTemplate.convertAndSend("seckill_goods_delay_normal_exchange",
                "seckill.goods.dead",
                key,
                (message) -> {
                    //获取消息的属性
                    MessageProperties messageProperties = message.getMessageProperties();
                    //设置过期时间
                    messageProperties.setExpiration((liveTime + 1800000)+"");

                    //测试时间
                    //messageProperties.setExpiration((10000) + "");

                    //返回
                    return message;
                });
    }

    /**
     * 构建一个库存长度的数组
     * @param goodsId
     * @param stockCount
     * @return
     */
    private String[] getIds(String goodsId, Integer stockCount){
        //初始化一个库存长度的数组
        String[] ids = new String[stockCount];
        //为数组的每个元素赋值
        for (int i = 0; i < stockCount; i++) {
            ids[i] = goodsId;
        }
        //返回
        return ids;
    }
}
